package http.nio;

import http.HttpRequest;
import http.HttpResponse;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

/**
 * @author fengbo
 * @version 1.0.0
 * @date 2018/11/29 14:09
 * @since JDK 1.8
 */
public class NioHttpServer {

    private final int port;
    private final CharsetDecoder decoder;


    private NioHttpServer(int port) {
        this.port = port;
        this.decoder = StandardCharsets.UTF_8.newDecoder();
    }

    private void listen() {

        ServerSocketChannel serverChannel;
        Selector selector;
        try {
            serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            selector = Selector.open();
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            serverChannel.bind(new InetSocketAddress(port));
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        while (true) {
            try {
                selector.select();
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                try {
                    if (key.isAcceptable()) {
                        SocketChannel client = ((ServerSocketChannel) key.channel()).accept();
                        if (client != null) {
                            client.configureBlocking(false);
                            client.register(selector, SelectionKey.OP_READ);
                        }
                    } else if (key.isReadable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        client.read(buffer);
                        buffer.flip();
                        String message = decoder.decode(buffer).toString();
                        HttpRequest request = new HttpRequest(message);
                        key.attach(request.getUrl());
                        key.interestOps(SelectionKey.OP_WRITE);
                    } else if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        HttpResponse response = new HttpResponse(key.attachment().toString());
                        byte[] bytes = response.getBody();
                        if (bytes != null) {
                            client.write(ByteBuffer.wrap(response.getHeader().getBytes()));
                            client.write(ByteBuffer.wrap(bytes));
                            client.close();
                        }
                    }
                } catch (IOException e) {
                    key.cancel();
                    closeChannel(key.channel());
                    e.printStackTrace();
                }
                keyIterator.remove();
            }

        }
    }

    private void closeChannel(Channel channel) {
        try {
            channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        NioHttpServer server = new NioHttpServer(8080);
        server.listen();
    }
}

